package org.streets.database;

import static org.streets.database.ConnectionConstants.ACQUIRE_INCREMENT;
import static org.streets.database.ConnectionConstants.CONNECTION_DIALECT;
import static org.streets.database.ConnectionConstants.CONNECTION_DRIVER;
import static org.streets.database.ConnectionConstants.CONNECTION_JNDI;
import static org.streets.database.ConnectionConstants.CONNECTION_PASSWORD;
import static org.streets.database.ConnectionConstants.CONNECTION_URL;
import static org.streets.database.ConnectionConstants.CONNECTION_USERNAME;
import static org.streets.database.ConnectionConstants.MAX_IDLE_TIME;
import static org.streets.database.ConnectionConstants.MAX_POOL_SIZE;
import static org.streets.database.ConnectionConstants.MAX_STATEMENTS;
import static org.streets.database.ConnectionConstants.MIN_POOL_SIZE;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.ScopeConstants;
import org.apache.tapestry5.ioc.annotations.EagerLoad;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Scope;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.services.ClassFactory;
import org.apache.tapestry5.ioc.services.PerthreadManager;
import org.apache.tapestry5.ioc.services.PropertyShadowBuilder;
import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
import org.apache.tapestry5.ioc.services.RegistryShutdownListener;
import org.apache.tapestry5.ioc.services.ThreadCleanupListener;
import org.slf4j.Logger;
import org.streets.commons.util.StringUtils;
import org.streets.context.ResourceLocator;
import org.streets.database.datadict.DataDict;
import org.streets.database.datadict.TableRelations;
import org.streets.database.datadict.impl.DataDictImpl;
import org.streets.database.datadict.impl.TableRelationsImpl;
import org.streets.database.dialect.Dialect;
import org.streets.database.dialect.OracleDialect;
import org.streets.database.internal.JPAEntityHandlerManager;
import org.streets.database.internal.PooledConnectionSourceImpl;
import org.streets.database.internal.SQLConnectionManagerImpl;
/**
 * 
 * @author dzb
 *
 */
public class SQLModule {
	
    /**
     * 避免Symbol出现异常的缺省配置
     * @param configuration
     */
    public void contributeFactoryDefaults(MappedConfiguration<String, String> configuration) {
    	configuration.add(CONNECTION_DRIVER, "jdbc.driver.ClassName");
    	configuration.add(CONNECTION_URL, "url");
    	configuration.add(CONNECTION_USERNAME, "user_name");
    	configuration.add(CONNECTION_PASSWORD, "password");    	
    	configuration.add(CONNECTION_JNDI, "datasource");
    	configuration.add(CONNECTION_DIALECT, " org.streets.database.dialect.OracleDialect");
    	configuration.add(MIN_POOL_SIZE, "3"); 
    	configuration.add(MAX_POOL_SIZE, "50");
    	configuration.add(ACQUIRE_INCREMENT, "5"); 
    	configuration.add(MAX_STATEMENTS, "0"); 
    	configuration.add(MAX_IDLE_TIME, "100");
    }

	public static ConnectionConfigurer buildSQLConfigurer(Logger logger,
		@Inject @Symbol(CONNECTION_DRIVER) String driver,
		@Inject @Symbol(CONNECTION_URL) String url,
		@Inject @Symbol(CONNECTION_USERNAME) String user,
		@Inject @Symbol(CONNECTION_PASSWORD) String pass,
		@Inject @Symbol(CONNECTION_JNDI) String jndi)  {
		ConnectionConfigurer cc = new ConnectionConfigurer(driver, url, user, pass);
		cc.setJNDI(jndi);
		return cc;
	}
	
	public static Dialect buildDialect(@Inject @Symbol(CONNECTION_DIALECT) String dialetName) {
		Dialect dialect = null;
		try {
			dialect = (Dialect) Class.forName(dialetName).newInstance();
		} catch (Exception ex) {
			// Do nothing ...
		}		
		if (dialect == null) {
			dialect = new OracleDialect();
		}
		return new OracleDialect();
	}

	public SQLConnectionSource buildPooledConnectionSource(ConnectionConfigurer cc, Logger logger,
			@Inject @Symbol(MIN_POOL_SIZE) int minPoolSize,
			@Inject @Symbol(MAX_POOL_SIZE) int maxPoolSize,
			@Inject @Symbol(ACQUIRE_INCREMENT) int increment,
			@Inject @Symbol(MAX_STATEMENTS) int maxStatements,
			@Inject @Symbol(MAX_IDLE_TIME) int maxIdleTime,
			RegistryShutdownHub registryShutdownHub) {
		
		final PooledConnectionSourceImpl pcs = new PooledConnectionSourceImpl(
				cc, logger, minPoolSize, maxPoolSize, 
				increment, maxStatements, maxIdleTime
				);
		// avoid ioc dependency
		registryShutdownHub.addRegistryShutdownListener(new RegistryShutdownListener(){
			public void registryDidShutdown() {
				pcs.shutdown();				
			}			
		});
		
		return pcs;
	}
	
	public static EntityPackageManager buildEntityPackageManager (
            final Collection<String> names) {
		
        return new EntityPackageManager() {
            public Collection<String> getNames() {
                return names;
            }
        };
    }	
    
	@EagerLoad
	@SuppressWarnings("unchecked")
	public static RecordHandlerManager buildEntityHandlerManager(Logger logger, ClassFactory classFactory,
    		EntityPackageManager packageHolder, ResourceLocator locator, Map<Class, RecordHandler> handlers) {
		
    	JPAEntityHandlerManager handlerManager = new JPAEntityHandlerManager(logger, classFactory, locator);  
    	//Registered contributed RecordHandler is priority
    	for (Iterator<Class> iter = handlers.keySet().iterator(); iter.hasNext();) {
    		Class clzz = iter.next();
    		RecordHandler<?> handler = handlers.get(clzz);
    		handlerManager.register(clzz, handler);
    	}
    	// secondly we process package contributed
    	for (String name : packageHolder.getNames()) {
    	    // judge whether the name is a class name
    	    int dot_pos = name.lastIndexOf('.');
    	    
    	    if (dot_pos < 0) continue;
    	    
	        String e_name = StringUtils.substring(name, dot_pos+1);
	        
	        if (Character.isUpperCase(e_name.charAt(0))) {
	            // is class name 
	            try {
                    Class<?> entity = Class.forName(name);
                    handlerManager.registerEntity(entity);
                } catch (ClassNotFoundException ex) {
                    logger.error("Can not load contributed class "+name, ex);
                }
	        } else {
	            // is regular package
	            handlerManager.registerPackage(name);
	        }
    	}
    	    	
    	return handlerManager;
    }
    
    /**
     * The connection manager manages connections on a per-thread/per-request basis. Any active transaction will be rolled
     * back at {@linkplain org.streets.context.HiveRegistry#cleanupThread() thread cleanup time}.  The thread is
     * cleaned up automatically in a streets web application.
     */
    @Scope(ScopeConstants.PERTHREAD)
	public static SQLConnectionManager buildConnectionManager(SQLConnectionSource source, Dialect dialect,
			RecordHandlerManager handlerManager,
			PerthreadManager perthreadManager) { 	
		final SQLConnectionManagerImpl cm = new SQLConnectionManagerImpl(source.getDataSource(), dialect, handlerManager);
		// thread clean
		perthreadManager.addThreadCleanupListener(new ThreadCleanupListener(){
			public void threadDidCleanup() {
				cm.clean();				
			}
		});
		return cm;
	}
	/**
	 * SQLConnection服务点
	 * 
	 * @param connectionManager
	 * @param propertyShadowBuilder
	 * @return
	 */
	public static SQLConnection buildSQLConnection(SQLConnectionManager connectionManager, 
			PropertyShadowBuilder propertyShadowBuilder) {
		// User PropertyShadowBuilder to proxy the executor
		return propertyShadowBuilder.build(connectionManager, "connection", SQLConnection.class);
	}
	/**
	 * 数据表间关系点
	 * 
	 * @param logger
	 * @param expresses
	 * @return
	 */
	public TableRelations buildTableRelations(Logger logger, Collection<String> expresses) {
		TableRelationsImpl relations = new TableRelationsImpl();
		for (String express : expresses) {
			relations.add(express);
		}
		return relations;
	}
	/**
	 * 数据字典服务点
	 * 
	 * @param logger
	 * @param locator
	 * @param packageManager
	 * @return
	 */
	public DataDict buildDataDict(Logger logger, ResourceLocator locator, EntityPackageManager packageManager) {
		DataDict dict = new DataDictImpl(logger, packageManager, locator);
		return dict;
	}

}
